#include "mywidget.h"
#include "my_shader.h"
#include <iostream>
#include <drm_fourcc.h>

#define MARGIN 0
static PFNEGLCREATEIMAGEKHRPROC egl_create_image_khr;
static PFNEGLDESTROYIMAGEKHRPROC egl_destroy_image_khr;
static PFNEGLCREATESYNCKHRPROC egl_create_sync_khr;
static PFNEGLDESTROYSYNCKHRPROC egl_destroy_sync_khr;
static PFNEGLCLIENTWAITSYNCKHRPROC egl_client_wait_sync_khr;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture_2_does;
PFNEGLDEBUGMESSAGECONTROLKHRPROC egl_debug_message_control_khr;

//    PFNEGLDEBUGMESSAGEINSERTKHRPROC eglDebugMessageInsertKHR =
//        (PFNEGLDEBUGMESSAGEINSERTKHRPROC)eglGetProcAddress("eglDebugMessageInsertKHR");
//	    if (!eglDebugMessageControlKHR) {
//        printf("EGL_KHR_debug not supported.\n");
//        return -1;
//    }

void EGLDebugCallback(EGLenum error, const char* command, EGLint msg_type,
                      EGLLabelKHR threadLabel, EGLLabelKHR objectLabel,
                      const char* message)
{
    fprintf(stderr, "[EGL DEBUG] Error=0x%x, Command=%s, Type=%d, Message=%s\n",
            error, command, msg_type, message);
}

static void GLCheckError(const char* stmt, const char* filename, int line)
{
    GLenum err = glGetError();
    if (err != GL_NO_ERROR) {
        printf("GL error after %s, ret:%d in %d\n", stmt, err, line);
        return;
    }
}
#define GL_CHECK(stmt) stmt; GLCheckError(#stmt, __FILE__, __LINE__);

MyWidget::MyWidget(QWidget *parent)
    : QOpenGLWidget(parent)
    , m_decoder(new Decoder)
{
    init_egl_functions();
//    connect(m_decoder, &Decoder::frameReceived, this, &MyWidget::frameReceived, Qt::QueuedConnection);
    connect(m_decoder, &Decoder::frameReceived, this, &MyWidget::frameReceived);
}

MyWidget::~MyWidget()
{
}

void MyWidget::start()
{
    m_decoder->start_a_gbm();
}

void MyWidget::initializeGL()
{
    initializeOpenGLFunctions();

    delete m_program;
    m_program = new QOpenGLShaderProgram;
    GL_CHECK(m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_shader));
    GL_CHECK(m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment_shader));
    delete m_oes_program;
    m_oes_program = new QOpenGLShaderProgram;
    GL_CHECK(m_oes_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_shader));
    GL_CHECK(m_oes_program->addShaderFromSourceCode(QOpenGLShader::Fragment, oes_fragment_shader));

    int position_location = 0;
    int texture_coord_location = 1;
    GL_CHECK(m_program->bindAttributeLocation("position", position_location));
    GL_CHECK(m_program->bindAttributeLocation("textcoord", texture_coord_location));
    GL_CHECK(m_oes_program->bindAttributeLocation("position", position_location));
    GL_CHECK(m_oes_program->bindAttributeLocation("textcoord", texture_coord_location));

    if (!m_oes_program->link())
        exit(1);
    if (!m_program->link())
        exit(1);

    delete m_vao;
    m_vao = new QOpenGLVertexArrayObject;
    m_vao->create();
    delete m_vbo;
    m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    m_vbo->create();
    delete m_ebo;
    m_ebo = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    m_ebo->create();

    m_vao->bind();
    m_vbo->bind();
    m_ebo->bind();

    static const float vertices[] = {
        //Position		//Texture coords
        1.0f,	1.0f,	1.0f,	1.0f,	//top right
        1.0f,	-1.0f,	1.0f,	0.0f,	//bottom right
        -1.0f,	-1.0f,	0.0f,	0.0f,	//bottom left
        -1.0f,	1.0f,	0.0f,	1.0f,	//top left
    };
    m_vbo->setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_vbo->allocate(vertices, sizeof(vertices));

    static const unsigned int indices[] = {
        0, 1, 3,
        1, 2, 3
    };
    m_ebo->setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_ebo->allocate(indices, sizeof(indices));

    //Position attribute
    glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), reinterpret_cast<void*>(0));
    glEnableVertexAttribArray(position_location);
    //Texture coord attribute
    glVertexAttribPointer(texture_coord_location, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), reinterpret_cast<void*>(2*sizeof(float)));
    glEnableVertexAttribArray(texture_coord_location);

    m_vao->release();
    m_ebo->release();
    m_vbo->release();
    GL_CHECK(glDisableVertexAttribArray(position_location));
    GL_CHECK(glDisableVertexAttribArray(texture_coord_location));

    //Retrieve uniforms
    m_oes_texture_sampler_location = m_oes_program->uniformLocation("tex");
    if (m_oes_texture_sampler_location == -1) {
        std::cout << "failed to retrieve OES texture sampler location" << std::endl;
        exit(1);
    }
    m_texture_sampler_location = m_program->uniformLocation("tex");
    if (m_texture_sampler_location == -1) {
        std::cout << "failed to retrieve texture sampler location" << std::endl;
        exit(1);
    }

    auto eglIface = QOpenGLContext::currentContext()->nativeInterface<QNativeInterface::QEGLContext>();
    m_egl_display = eglIface->display();
}

void MyWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void MyWidget::paintGL()
{
    if (m_flag)
        do_egl_image(m_frame);

    GL_CHECK(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
    GL_CHECK(glClear(GL_COLOR_BUFFER_BIT));
    //Draw video
    if (m_frame_buffer) {
        GL_CHECK(m_oes_program->bind());
        //Textures
        GL_CHECK(glActiveTexture(GL_TEXTURE0));
        glUniform1i(m_texture_sampler_location, 0);
        GL_CHECK(glBindTexture(GL_TEXTURE_2D, m_frame_buffer->texture_));
        //Draw elements
        m_vao->bind();
        GL_CHECK(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
        //Cleanup
        m_vao->release();
        glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        m_oes_program->release();
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    destroy_egl_frames();
}

void MyWidget::frameReceived(const FrameStruct &frame)
{
    m_frame = frame;
    m_flag = true;
    update();
}

void MyWidget::init_egl_functions()
{
    egl_create_image_khr = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
    egl_destroy_image_khr = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
    egl_create_sync_khr = reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(eglGetProcAddress("eglCreateSyncKHR"));
    egl_destroy_sync_khr = reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(eglGetProcAddress("eglDestroySyncKHR"));
    egl_client_wait_sync_khr = reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC>(eglGetProcAddress("eglClientWaitSyncKHR"));
    gl_egl_image_target_texture_2_does = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
    egl_debug_message_control_khr = reinterpret_cast<PFNEGLDEBUGMESSAGECONTROLKHRPROC>(eglGetProcAddress("eglDebugMessageControlKHR"));
    if ((egl_create_image_khr == nullptr) || (egl_destroy_image_khr == nullptr) || (egl_create_sync_khr == nullptr) || (egl_destroy_sync_khr == nullptr)
            || (egl_client_wait_sync_khr == nullptr) || (gl_egl_image_target_texture_2_does == nullptr)
            || (egl_debug_message_control_khr == nullptr)
            ) {
      std::cout << "Failed to retrieve EGL functions" << std::endl;
      exit(1);
    }
    egl_debug_message_control_khr(EGLDebugCallback, NULL);
}

void MyWidget::init_gl_texture(int format, int width, int height, int stride, int shmid, int gl_texture_id, void *imgdata)
{

}

void MyWidget::displayInvalidateGL(int x, int y, int width, int height)
{

}

void MyWidget::do_egl_image(const FrameStruct &frame)
{
//    m_mpp_colour_space = frame.mpp_colour_space;
//    m_mpp_colour_range = frame.mpp_colour_range;
//    m_mpp_colour_primaries = frame.mpp_colour_primaries;

//    auto e = m_egl_images.find(frame.mpp_buffer);
//    if (e != m_egl_images.end()) {
//        if ((e->second.colour_space_ != frame.mpp_colour_space) || (e->second.colour_range_ != frame.mpp_colour_range)
//                || (e->second.width_ != frame.width) || (e->second.height_ != frame.height)) {
//            printf("MPP buffer format changed, resetting EGL images\n");
//            destroy_egl_frames();
//            e = m_egl_images.end();
//        }
//    }

//    if (e == m_egl_images.end()) {
        EGLint atts[] = {
            EGL_WIDTH, static_cast<EGLint>(frame.width),
            EGL_HEIGHT, static_cast<EGLint>(frame.height),
            EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888,
            EGL_DMA_BUF_PLANE0_FD_EXT, frame.fd,
            EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(frame.offset_x),
            EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(frame.hor_stride),
//            EGL_DMA_BUF_PLANE1_FD_EXT, frame.fd,
//            EGL_DMA_BUF_PLANE1_OFFSET_EXT, static_cast<EGLint>(frame.offset_x + frame.hor_stride * frame.ver_stride),
//            EGL_DMA_BUF_PLANE1_PITCH_EXT, static_cast<EGLint>(frame.hor_stride),
            EGL_YUV_COLOR_SPACE_HINT_EXT, EGL_ITU_REC601_EXT,
            EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_FULL_RANGE_EXT,
            EGL_NONE
        } ;

        EGLImageKHR egl_image = egl_create_image_khr(m_egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, atts);
        if (egl_image == EGL_NO_IMAGE_KHR) {
            printf("failed to create egl image\n");
            GLenum err = glGetError();
            if (err != GL_NO_ERROR) {
                printf("GL error ret:%d\n", err);
            }
            exit(1);
        }

//        e = m_egl_images.insert(std::make_pair(frame.mpp_buffer, EGL_FRAME(egl_image, frame.mpp_colour_space, frame.mpp_colour_range,
//                                                            frame.width, frame.height))).first;
//    }

    if (m_frame_buffer == nullptr) {
        GLuint frm = GL_INVALID_VALUE;
        glGenFramebuffers(1, &frm);
        glBindFramebuffer(GL_FRAMEBUFFER, frm);
        GLuint frame_buffer_texture = GL_INVALID_VALUE;
        glGenTextures(1, &frame_buffer_texture);
        glBindTexture(GL_TEXTURE_2D, frame_buffer_texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frame.width, frame.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_buffer_texture, 0);
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
            printf("failed to create frame buffer\n");
            exit(1);
        }
        m_frame_buffer = std::make_unique<FRAME_BUFFER>(frm, frame_buffer_texture, frame.width, frame.height);
        printf("bybobbi: first!!!!!!!, frame.width is %d, frame.height is %d\n", frame.width, frame.height);
    } else {
        glBindFramebuffer(GL_FRAMEBUFFER, m_frame_buffer->frame_);
    }

    GL_CHECK(m_oes_program->bind());
    //Textures
    GL_CHECK(glActiveTexture(GL_TEXTURE0));
    GL_CHECK(glUniform1i(m_oes_texture_sampler_location, 0));
//    GL_CHECK(gl_egl_image_target_texture_2_does(GL_TEXTURE_EXTERNAL_OES, e->second.image_));
    GL_CHECK(gl_egl_image_target_texture_2_does(GL_TEXTURE_EXTERNAL_OES, egl_image));
    //Draw elements
    GL_CHECK(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
    GL_CHECK(glClear(GL_COLOR_BUFFER_BIT));
    GL_CHECK(m_vao->bind());
    GL_CHECK(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
    //Cleanup
    GL_CHECK(m_vao->release());
    GL_CHECK(glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0));
    GL_CHECK(glBindTexture(GL_TEXTURE_2D, 0));
    m_oes_program->release();
    GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));

    //Sync
    const EGLSyncKHR egl_sync = egl_create_sync_khr(m_egl_display, EGL_SYNC_FENCE_KHR, nullptr);
    if (egl_sync == EGL_NO_SYNC_KHR) {
        printf("failed to create egl sync object\n");
    } else {
        if (egl_client_wait_sync_khr(m_egl_display, egl_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR)
                != EGL_CONDITION_SATISFIED_KHR) {
            printf("failed to sync egl buffers\n");
        }
        if (egl_destroy_sync_khr(m_egl_display, egl_sync) == EGL_FALSE) {
            printf("failed to destroy egl sync object\n");
        }
    }
}

void MyWidget::destroy_egl_frames()
{

}

